﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BbsbWebService.Classes;
using System.Xml.Linq;
using System.IO;
using System.Reflection;
using BbsbWebService.Enums;
using BbsbWebService.Repositories;
using BbsbWebService.Logging;
using BbsbWebService.Classes.Plugins.Bbsb;
using BbsbWebService.Classes.Plugins.KeyMap;
using BbsbWebService.Tasks;
using BbsbWebService.Classes.Plugins;
using BbsbWebService.Classes.Controllers;

namespace BbsbWebService
{
    public class AppSettings
    {
        #region Settings Dictionary

        private static Lazy<Dictionary<string, string>> _settingsDictionary = new Lazy<Dictionary<string, string>>(() =>
            {
                var dictionary = new Dictionary<string, string>();
                if (File.Exists(SettingsFile.FullName))
                {
                    try
                    {
                        XDocument doc = XDocument.Load(SettingsFile.FullName);

                        var settings = doc.Descendants("AppSettings");
                        if (settings.Count() == 1)
                        {
                            var nodes = settings.Single().Descendants();
                            foreach (var elem in nodes)
                            {
                                dictionary.Add(elem.Name.ToString(), elem.Value);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Logging.Logger.Instance.LogException(ex, "Error trying to initialise app settings");
                    }
                }
                return dictionary;
            }, true);

        private static Dictionary<string, string> SettingsDictionary
        {
            get
            {
                return _settingsDictionary.Value;
            }
        }

        #endregion

        #region Private Members

        private static AppSettings _instance;
        private static Dictionary<string, string> _mimeTypes;
        private static readonly int defaultPort = 40700;

        private static DirectoryInfo ApplicationDirectory
        {
            get
            {
                FileInfo execAss = new FileInfo(Assembly.GetExecutingAssembly().Location);
                return execAss.Directory;
            }
        }

        
        #endregion

        #region Public Static Properties

        public static Dictionary<Type, Lazy<IController>> ControllersTypes { get; private set; }
        public static Dictionary<string, Type> ItemTypeDictionary { get; private set; }

        public static AppSettings Instance
        {
            internal get
            {
                if (_instance == null)
                {
                    _instance = new AppSettings();
                }
                return _instance;
            }
            set
            {
                _instance = value;
            }
        }

        public static DirectoryInfo WebPageBaseDirectory
        {
            get
            {
                DirectoryInfo dir = new DirectoryInfo(Path.Combine(ApplicationDirectory.FullName, "Web"));
                dir.EnsureDirectoryExistance();
                return dir;
            }
        }

        public static FileInfo SettingsFile
        {
            get
            {
                string path = Path.Combine(ApplicationDirectory.FullName, "BbsbWebServiceSettings.xml");
                return new FileInfo(path);
            }
        }

        public static Dictionary<string, string> MimeTypes
        {
            get
            {
                if (_mimeTypes == null)
                {
                    _mimeTypes = new Dictionary<string, string>();
                    _mimeTypes.Add("bmp", "image/bmp");
                    _mimeTypes.Add("css", "text/css");
                    _mimeTypes.Add("gif", "image/gif");
                    _mimeTypes.Add("htm", "text/html");
                    _mimeTypes.Add("html", "text/html");
                    _mimeTypes.Add("jpeg", "image/jpeg");
                    _mimeTypes.Add("jpg", "image/jpeg");
                    _mimeTypes.Add("js", "application/x-javascript");
                    _mimeTypes.Add("manifest", "text/cache-manifest");
                    _mimeTypes.Add("png", "image/png");
                    _mimeTypes.Add("tif", "image/tiff");
                    _mimeTypes.Add("tiff", "image/tiff");
                }
                return _mimeTypes;
            }
        }

        #endregion

        public DateTime ApplicationStartTime { get; set; }

        public int Port { get; set; }

        public AppSettings()
        {
            ApplicationStartTime = DateTime.Now;
        }

        public virtual void Initialise()
        {
            Port = GetItemFromSettings("Port", defaultPort);

            InitialisePluginTypes();
        }

        public void Save()
        {
            XElement items = RepositoryInstances.ItemRepository.SaveRepository();

            XElement groups = RepositoryInstances.GroupRepository.SaveRepository();

            XElement tasks = RepositoryInstances.TaskRepository.SaveRepository();

            List<XElement> appSettingsElements = new List<XElement>();
            appSettingsElements.Add( new XElement("Port", Port));
            appSettingsElements.AddRange(GetExtraSaveFields());

            // iterate over any Controller/Item specific values that aren't defined in the settings
            foreach (var item in SettingsDictionary)
            {
                if (!appSettingsElements.Any(o => o.Name.LocalName == item.Key))
                {
                    appSettingsElements.Add(new XElement(item.Key, item.Value));
                }
            }

            XElement appSettings = new XElement("AppSettings", appSettingsElements);

            XElement baseElement = new XElement("Settings",
                appSettings,
                items,
                groups,
                tasks);

            XDocument doc = new XDocument(baseElement);
            doc.Save(SettingsFile.FullName);
        }

        public virtual IEnumerable<XElement> GetExtraSaveFields()
        {
            return null;
        }

        public static T GetItemFromSettings<T>(string xmlName, T defaultVal)
        {
            object retVal = defaultVal;

            if (SettingsDictionary.ContainsKey(xmlName))
            {
                try
                {
                    string value = SettingsDictionary[xmlName];
                    retVal = value.ParseValueAsType(defaultVal);
                }
                catch (Exception ex)
                {
                    Logging.Logger.Instance.LogException(ex, "Error trying to get the value for '{0}'", xmlName);
                }
            }

            return (T)retVal;
        }

        public static IEnumerable<Type> PluginItemTypes()
        {
            List<Type> types = new List<Type>();
            foreach (var item in ItemTypeDictionary)
            {
                types.Add(item.Value);
            }

            return types;
        }

        public static IEnumerable<Type> TaskTypes()
        {
            List<Type> types = new List<Type>{
                typeof(ScheduledTask),
                typeof(OneOffTask),
                typeof(RecurringTask)
            };
            return types;
        }

        public static IEnumerable<Type> AllServiceKnownTypes(ICustomAttributeProvider provider)
        {
            List<Type> list = new List<Type>();
            list.AddRange(PluginItemTypes());
            list.AddRange(TaskTypes());
            return list;
        }

        private void InitialisePluginTypes()
        {
            List<Type> items = new List<Type>{
                typeof(BbsbItem),
                typeof(HomeEasyItem),
                typeof(KeyMapItem)
            };
            // TODO: Get plugins from plugin directory...

            ControllersTypes = new Dictionary<Type, Lazy<IController>>();
            ItemTypeDictionary = new Dictionary<string, Type>();

            foreach (var item in items)
            {
                // Check that it's a plugin item
                var attribList = item.GetCustomAttributes(typeof(BbsbWsPluginItemAttribute), true);
                if (attribList.Length > 0)
                {
                    // Is a plugin, so add it to the dictionary..
                    var name = item.Name.Replace("Item", string.Empty);
                    ItemTypeDictionary.Add(name, item);

                    // Get Controller type (not entirely happy with this method, but can't think of another way..)
                    ControllableItem tempItem = (ControllableItem)Activator.CreateInstance(item);
                    Type controllerType = tempItem.ControllerType;
                    if (!ControllersTypes.ContainsKey(controllerType))
                    {
                        ControllersTypes.Add(controllerType, new Lazy<IController>(() =>
                            {
                                return (IController)Activator.CreateInstance(controllerType);
                            }));
                    }
                }
            }
        }
    }
}
